﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using SharedGenomics.TextMining;

namespace SharedGenomics.Workbench.Tests.TextMining
{
    [TestClass]
    public class ScoredTermCollectionTests
    {
        private ScoredTermCollection col;

        [TestInitialize]
        public void Setup()
        {
            this.col = new ScoredTermCollection();
        }

        [TestMethod]
        public void DefaultConstructorTest()
        {
            ScoredTermCollection stc = new ScoredTermCollection();
            Assert.AreEqual(0, stc.Count);
        }

        [TestMethod]
        public void AddTestNewKey()
        {
            this.col.Add("term", 0.1);

            Assert.AreEqual(1, this.col.Count);
            Assert.IsTrue(this.col.Contains(new ScoredTerm("term", 0.1)));
        }

        [TestMethod]
        public void AddTestExistingKey()
        {
            //NOTE: cvalue scores are used as the keys in the collection
            this.col.Add("term1", 0.1);
            this.col.Add("term2", 0.1);

            Assert.AreEqual(2, this.col.Count);
            Assert.IsTrue(this.col.Contains(new ScoredTerm("term2", 0.1)));
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void AddTestNullTerm()
        {
            this.col.Add(new ScoredTerm(null, 0.1));
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void AddTestEmptyTerm()
        {
            this.col.Add(new ScoredTerm(String.Empty, 0.1));
        }

        [TestMethod]
        public void AddTestScoredTerm()
        {
            ScoredTerm t = new ScoredTerm("term", 0.1);
            this.col.Add(t);

            Assert.AreEqual(1, this.col.Count);
            Assert.IsTrue(this.col.Contains(t));
        }

        [TestMethod]
        public void AddTestDuplicate()
        {
            ScoredTerm t1 = new ScoredTerm("term", 0.1);
            ScoredTerm t2 = new ScoredTerm("term", 0.1);

            this.col.Add(t1);
            this.col.Add(t2);

            Assert.AreEqual(1, this.col.Count);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void AddTestNull()
        {
            this.col.Add(null);
        }

        [TestMethod]
        public void ClearTest()
        {
            this.col.Add("term1", 0.1);
            this.col.Add("term2", 0.1);
            this.col.Add("term3", 0.1);

            Assert.AreEqual(3, this.col.Count);

            this.col.Clear();
            Assert.AreEqual(0, this.col.Count);
        }

        [TestMethod]
        public void RemoveTestPresent()
        {
            this.col.Add("term", 0.1);

            Assert.IsTrue(this.col.Remove(new ScoredTerm("term", 0.1)));
        }

        [TestMethod]
        public void RemoveTestNotPresent()
        {
            Assert.IsFalse(this.col.Remove(new ScoredTerm("term", 0.0)));
        }

        [TestMethod]
        public void RemoveTestNull()
        {
            Assert.IsFalse(this.col.Remove(null));
        }

        [TestMethod]
        public void CopyToTestValid()
        {
            ScoredTerm t1 = new ScoredTerm("term1", 0.1);
            ScoredTerm t2 = new ScoredTerm("term1", 0.2);
            ScoredTerm t3 = new ScoredTerm("term1", 0.3);

            this.col.Add(t1);
            this.col.Add(t2);
            this.col.Add(t3);

            ScoredTerm[] terms = new ScoredTerm[3];

            this.col.CopyTo(terms, 0);
            CollectionAssert.AreEqual(new[] { t1, t2, t3 }, terms);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void CopyToTestNullArray()
        {
            this.col.CopyTo(null, 0);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CopyToTestNegativeIndex()
        {
            this.col.Add("term1", 0.1);

            ScoredTerm[] terms = new ScoredTerm[1];

            this.col.CopyTo(terms, -1);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void CopyToTestArrayIndexOutOfRange()
        {
            this.col.Add("term1", 0.1);

            ScoredTerm[] terms = new ScoredTerm[1];

            this.col.CopyTo(terms, 1);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void CopyToTestArrayTooSmall()
        {
            this.col.Add("term1", 0.1);
            this.col.Add("term2", 0.1);

            ScoredTerm[] terms = new ScoredTerm[1];

            this.col.CopyTo(terms, 0);
        }

        [TestMethod]
        public void GetEnumeratorTestUniqueKeys()
        {
            ScoredTerm t1 = new ScoredTerm("term1", 0.6);
            ScoredTerm t2 = new ScoredTerm("term2", 0.2);
            ScoredTerm t3 = new ScoredTerm("term3", 0.9);

            this.col.Add(t1);
            this.col.Add(t2);
            this.col.Add(t3);

            //terms should be output in ascending cValue order
            CollectionAssert.AreEqual(new[] { t2, t1, t3 }, this.col.ToArray());
        }

        [TestMethod]
        public void GetEnumeratorTestDuplicateKeys()
        {
            ScoredTerm t1 = new ScoredTerm("term1", 0.6);
            ScoredTerm t2 = new ScoredTerm("term2", 0.2);
            ScoredTerm t3 = new ScoredTerm("term3", 0.2);
            ScoredTerm t4 = new ScoredTerm("term4", 0.9);

            double? cVal = null;

            //check cValues are in ascending order
            //NOTE: the exact order of enumeration cannot be guaranteed
            foreach (var term in this.col)
            {
                if (!cVal.HasValue)
                {
                    cVal = term.CValue;
                }
                else
                {
                    Assert.IsTrue(term.CValue >= cVal.Value);
                    cVal = term.CValue;
                }
            }
        }

        [TestMethod]
        public void GetTermsStartingWithTestEmptyCollection()
        {
            Assert.AreEqual(0, this.col.GetTermsStartingWith('a').Count);
        }

        [TestMethod]
        public void GetTermsStartingWithTestSingle()
        {
            this.col.Add("word", 0.1);
            Assert.AreEqual(1, this.col.GetTermsStartingWith('w').Count);
        }

        [TestMethod]
        public void GetTermsStartingWithTestMultiple()
        {
            this.col.Add("this is a word", 0.1);
            this.col.Add("this is another word", 0.1);
            this.col.Add("this is yet another word", 0.1);
            this.col.Add("abc", 0.1);

            var terms = this.col.GetTermsStartingWith('t');

            Assert.AreEqual(3, terms.Count);
            Assert.IsTrue(terms.Contains(new ScoredTerm("this is a word", 0.1)));
            Assert.IsTrue(terms.Contains(new ScoredTerm("this is another word", 0.1)));
            Assert.IsTrue(terms.Contains(new ScoredTerm("this is yet another word", 0.1)));
        }

        [TestMethod]
        public void GetTermsStartingWithTestCaseInsensitivity()
        {
            this.col.Add("this is A Word", 0.1);
            this.col.Add("THIS is another", 0.1);

            var terms = this.col.GetTermsStartingWith('t');

            //both terms should be returned
            Assert.AreEqual(2, terms.Count);
        }

        [TestMethod]
        public void GetTermsStartingWithTestNone()
        {
            this.col.Add("bunch of words", 0.1);
            this.col.Add("some more", 0.1);
            this.col.Add("yet more", 0.1);

            Assert.AreEqual(0, this.col.GetTermsStartingWith('a').Count);
        }

        [TestMethod]
        public void GetTermQuartileSimple()
        {
            //each term should be in its own quartile
            var terms = this.CreateTerms(new[]{0.1, 0.2, 0.3, 0.4});
            this.AddToCollection(terms);

            this.AssertQuartiles(terms, new[] { 1, 2, 3, 4 });
        }

        [TestMethod]
        public void GetTermQuartileDuplicateKeys()
        {
            //t1 and t2 have the same cValue score (NOTE: cValues are keys in the collection)
            var terms = this.CreateTerms(new[]{0.1, 0.1, 0.3, 0.4});
            this.AddToCollection(terms);

            //t1 and t2 have the same cValue score and so should be in the same quartile - this means Q2 should be empty
            this.AssertQuartiles(terms, new[] { 1, 1, 3, 4 });
        }

        [TestMethod]
        public void GetTermQuartileSkewedDistribution()
        {
            var terms = this.CreateTerms(new[]{0.1, 0.1, 0.1, 0.4});
            this.AddToCollection(terms);

            //t1, t2 and t3 should all be in the first quartile, t4 in the 4th
            this.AssertQuartiles(terms, new[] { 1, 1, 1, 4 });
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void GetTermQuartileTestInvalidNull()
        {
            int q = this.col.GetTermQuartile(null);
        }

        private void AddToCollection(IEnumerable<ScoredTerm> terms)
        {
            if (terms != null)
            {
                foreach (var t in terms)
                    this.col.Add(t);
            }
        }

        private void AssertQuartiles(ScoredTerm[] terms, int[] quartiles)
        {
            for (int i = 0; i < terms.Length; i++)
            {
                Assert.AreEqual(quartiles[i], this.col.GetTermQuartile(terms[i]));
            }
        }

        private ScoredTerm[] CreateTerms(IEnumerable<double> cValues)
        {
            List<ScoredTerm> terms = new List<ScoredTerm>(cValues.Count());

            int termNumber = 1;
            foreach (double d in cValues)
            {
                //NOTE: side-effect in String.Format
                terms.Add(new ScoredTerm(String.Format("term{0}", termNumber++), d));
            }

            return terms.ToArray();
        }
    }
}
